/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.epam.expertsys;

import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Date;
import java.sql.*;

import java.io.*;
import java.util.List;

/**
 *
 * @author Admin
 */
public final class FactsKnown {

    private static Logger log = Logger.getLogger(FactsKnown.class);
    private static FactsKnown instance;
    private Set<String> variablesInitialised;
    private ArrayList<Fact> Facts;
    private int factsInList;

    private FactsKnown() {
        this.Facts = new ArrayList<Fact>();
        this.variablesInitialised = new HashSet<String>();
        this.factsInList = 0;
    }

    /**
     * Attempts to add newFact to facts list
     * If fact with the same varName and same varValue is already in list,
     * does nothing
     * If fact with the same varName and different varValue is already in list,
     * throws VariableRedefinedException
     * @param newFact
     * @throws VariableRedefinedException
     */
    public void addFact(Fact newFact) throws VariableRedefinedException {
        if (this.variablesInitialised.contains(newFact.getVarName())) {
            if (!(this.Facts.contains(newFact))) {
                throw new VariableRedefinedException(newFact.getVarName()
                        + ": attempt to redefine");
            } else {
                log.warn(newFact.getVarName() + " not added; variable already defined");
            }
        }
        if (!(this.Facts.contains(newFact))) {
            this.variablesInitialised.add(newFact.getVarName());
            this.Facts.add(newFact);
            this.factsInList++;


            if (this.variablesInitialised.size() != (this.factsInList)) {
                this.factsInList = this.variablesInitialised.size();
                log.error("Out of memory; fact not added to list");

            }
        }
    }

    /**
     * Checks whether fact with varName name is already in list;
     * returns true if it is, false otherwise
     * @param varName
     * @return
     */
    public boolean isDefined(String varName) {
        boolean flag = false;
        for (int index = 0; index < this.factsInList; index++) {
            if (this.getFact(index).getVarName().equals(varName)) {
                flag = true;
            }
        }

        return flag;
    }

    public Fact getFact(int index) {
        return this.Facts.get(index);
    }

    /**
     * Returns number of facts in list
     * @return
     */
    public int numberFacts() {
        return this.factsInList;
    }

    /**
     * Singleton instance getter
     * @return
     */
    public static FactsKnown getInstance() {
        if (instance == null) {
            instance = new FactsKnown();
        }
        return instance;
    }

    /**
     * Returns facts list
     * @return
     */
    public List<Fact> getFacts() {
        //return (ArrayList<Fact>)this.Facts.clone();
        List<Fact> getAr = new ArrayList<Fact>();

        for (int index = 0; index < this.factsInList; index++) {
            getAr.add(this.Facts.get(index));
        }

        return getAr;
    }

    /**
     * Prints facts to stdout in predefined format
     */
    public void printFacts() {
        for (int index = 0; index < this.factsInList; index++) {
            System.out.println("Fact #" + (index + 1) + ": "
                    + this.Facts.get(index).getVarName() + " = "
                    + this.Facts.get(index).getVarValue());
        }
    }

    /**
     * This method is not reccomended as required file-format is awkward
     * and counterintuitive. Use readFactsJsonFile(String fileName) instead.
     *
     * Reads facts list from file. Input file should use following syntax:
     * Number of facts
     * //following block should be repeated "Number of facts" times
     * varName
     * varValue
     * //
     *
     * @deprecated
     * @param fileName - name of input file
     */
    public void readFactsFile(String fileName) {
        //TOASK: не дофига ли жрёт?
        try {
            FileReader fileReader = new FileReader(fileName);
            BufferedReader bufReader = new BufferedReader(fileReader);
            //bufReader.read();
            int numNewFacts = Integer.parseInt(bufReader.readLine());
            for (int counter = 0; counter < numNewFacts; counter++) {
                String bufName = bufReader.readLine();
                String bufVal = bufReader.readLine();
                this.addFact(Fact.create(bufName, bufVal));
            }
        } catch (FileNotFoundException er) {
            log.error("File " + fileName + " not found");
        } catch (IOException er) {
            log.error("File " + fileName + " closed");
        } catch (VariableRedefinedException er) {
            log.error("Attempt to override variable; "
                    + "reading terminated");
        } catch (NullValueException er) {
            log.error("File corrupted; reading terminated");
        }
    }

    /**
     * Reads factsList from file fileName
     * throws NotJsonFormatException if input is not a well-formed JSON file
     * throws RecNotClassCompatibleException if input doesn't fit data format
     * @param fileName
     * @throws RecNotClassCompatibleException
     * @throws NotJsonFormatException
     * @throws VariableRedefinedException
     */
    public void readFactsJsonFile(String fileName) throws
            RecNotClassCompatibleException, NotJsonFormatException,
            VariableRedefinedException {
        //TODO: Replace RecNotClassCompatibleException with standart DataFormatException
        FileReader fileReader = null;
        BufferedReader bufReader = null;
        boolean hasStream = true;
        boolean hasNext = true;
        try {
            fileReader = new FileReader(fileName);
            bufReader = new BufferedReader(fileReader);
        } catch (IOException er) {
            hasStream = false;
            log.error("Inputstream problems; No reading");
        }

        if (hasStream) {
            Fact newFact = null;
            while (hasNext) {
                try {
                    newFact = JsonParse.factParse(bufReader);
                } catch (IOException er) {
                    log.warn("Strange behavior; IOException caught while parsing");
                } catch (NullValueException er) {
                    hasNext = false;
                    break;
                }
                FactsKnown.instance.addFact(newFact);

            }

        }
        try {
            bufReader.close();
        } catch (IOException er) {
            log.warn("Strange behavior; IOException caught while trying to close bufReader");
        }
    }

    public void writeFactsFile(String fileName) {
        FileWriter fileWriter = null;
        BufferedWriter bufWriter = null;
        PrintWriter printWriter = null;
        boolean hasStream = true;
        try {
            fileWriter = new FileWriter(fileName);
            bufWriter = new BufferedWriter(fileWriter);
            printWriter = new PrintWriter(bufWriter);
        } catch (IOException er) {
            hasStream = false;
            log.error("Outputstream problems; no writing");
        }

        if (hasStream) {
            printWriter.println(factsInList);

            for (int counter = 0; counter < factsInList; counter++) {
                //bufWriter.newLine();
                printWriter.println(this.Facts.get(counter).getVarName());
                //bufWriter.newLine();
                printWriter.println(this.Facts.get(counter).getVarValue());
            }
            //printWriter.close();
            try {
                bufWriter.close();
                fileWriter.close();
            } catch (IOException e) {
                log.warn("Strange behavior; IOException caught while trying to close Writers");
            }
        }

    }

    public void writeFactsDB(String url, String login, String pass,
            int curDoc, int curPat) throws SQLException {
        Connection connect = DriverManager.getConnection(url, login, pass);
        PreparedStatement prepStat = connect.prepareStatement("insert into facts (FACT_DATE, FACTNAME, FACTVAL, PATIENT_ID, DOCTOR_ID)"
                + " values (?, ?, ?, ?, ?)");
        prepStat.setLong(1, new Date().getTime());
        prepStat.setLong(4, curPat);
        prepStat.setLong(5, curDoc);
        for (int factInd = 0; factInd < this.factsInList; factInd++) {
            prepStat.setString(2, this.getFact(factInd).getVarName());
            prepStat.setString(3, this.getFact(factInd).getVarValue());

            prepStat.executeUpdate();
        }


    }

    /**
     * Clears the facts list
     */
    public void clear() {
        this.Facts.clear();
        this.variablesInitialised.clear();
        this.factsInList = 0;
    }
}
